package p2pcdnsim.network;

import java.util.Random;

import desmoj.core.simulator.Model;
import desmoj.core.simulator.SimProcess;

public class TCPConnection extends SimProcess {

	public static final int CLOSED = 0;
	public static final int LISTEN = 1;
	public static final int SYN_SENT = 2;
	public static final int SYN_RECEIVED = 3;
	
	Random randomGen;
	
	NetworkAddress localAddress;
	NetworkAddress destinationAddress;
	int localPort,destinationPort;
	TCPControl control;
	TransportLayer tLayer;
	Socket socket;
	
	public TCPConnection(Model owner, TransportLayer tLayer, NetworkAddress localAddress,int localPort,NetworkAddress destinationAddress,int destinationPort) {
		super(owner,"TCPConnection",false);
		this.localAddress = localAddress;
		this.localPort = localPort;
		this.destinationAddress = destinationAddress;
		this.destinationPort = destinationPort;
		this.tLayer = tLayer;
		control = new TCPControl();
		randomGen = new Random();
	}

	@Override
	public void lifeCycle() {
		this.passivate();
		
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	/**
	 * Sets the maximum segment size
	 * @param mss
	 */
	public void setMSS(long mss) {
		control.setMSS(mss);
		
	}

	public void open(int timeout) {
		//TODO: improve exception
		if(control.Current_State != CLOSED) throw new RuntimeException("Cannot open");
		int seq = randomGen.nextInt();
		TCPSegment newSegment = new TCPSegment(localAddress,localPort,destinationAddress,destinationPort);
		newSegment.setSEQ(seq);
		newSegment.setSYN(true);
		newSegment.setLEN(1);
		control.setSND_UNA(seq);
		control.setSND_NXT(seq+1);
		control.setUserTimeout(timeout);
		
		control.setState(SYN_SENT);
		
		
		transmitSegment(newSegment);
	}

	private void transmitSegment(TCPSegment newSegment) {
		// TODO Think about how to handle timeout
		tLayer.transmitDatagram(newSegment,this.currentTime());
		
	}

	public void receiveTCPSegment(TCPSegment content) {

		
		if(content.getSYN()) handleIncomingSYN(content);
		
	}

	private void handleIncomingSYN(TCPSegment segment) {
		switch(control.getState()) {
		case LISTEN:
			control.setRCV_NXT(segment.getSEQ()+1);
			control.setIRS(segment.getSEQ());
			
			//Create a SYN ACK
			TCPSegment newSegment = new TCPSegment(localAddress,localPort,destinationAddress,destinationPort);
			control.setISS(randomGen.nextInt());
			newSegment.setSEQ(control.getISS());
			newSegment.setACK(control.getRCV_NXT());
			newSegment.setSYN(true);
			newSegment.setACK(true);
			newSegment.setLEN(1);			
			newSegment.setWND(control.getRCV_WND());

			control.setSND_NXT(control.getISS()+1);
			control.setSND_UNA(control.getISS());
			
			control.setState(SYN_RECEIVED);
			
			transmitSegment(newSegment);
			
			break;
			
			
		case SYN_SENT:
			System.out.println("...");

			//Update Status info in the control
			control.setRCV_NXT(segment.getSEQ()+1);
			control.setIRS(segment.getSEQ());
			control.setSND_WND(segment.getWND());
			control.setSND_WL1(segment.getSEQ());
			control.setSND_WL2(segment.getACK());
			
			//TODO: continue programing from here...
			
			break;
		}
		
	}

	void setState(int state) {
		control.setState(state);
	}

	
	private class TCPControl {
		int SND_UNA;
		int SND_NXT;
		long SND_WND;

		public long getSND_WND() {
			return SND_WND;
		}

		public void setSND_WND(long snd_wnd) {
			SND_WND = snd_wnd;
		}

		public int getSND_WL1() {
			return SND_WL1;
		}

		public void setSND_WL1(int snd_wl1) {
			SND_WL1 = snd_wl1;
		}

		public int getSND_WL2() {
			return SND_WL2;
		}

		public void setSND_WL2(int snd_wl2) {
			SND_WL2 = snd_wl2;
		}

		public int getSND_UNA() {
			return SND_UNA;
		}

		public int getSND_NXT() {
			return SND_NXT;
		}

		public long getMSS() {
			return MSS;
		}

		public void setRCV_WND(long rcv_wnd) {
			RCV_WND = rcv_wnd;
		}

		int SND_WL1;
		int SND_WL2;

		int ISS;			

		int RCV_NXT;
		long RCV_WND=10240;
		int IRS;

		long Current_Measured_RTT=1000;
		long Current_Calculated_RTO=500;

		long MSS=1500;
		int Send_Segment_Ratio=100;

		boolean Delayed_ACK=true;
		boolean Persist_Timer=true;

		int persist_segment_number;
		boolean persist_seg_sent=false;

		int Current_State= CLOSED;
		int Previous_State= CLOSED;

		long RTO_timer_granularity=1;

		//Constants for RTO calculation using the original RFC 793 method.
		double ALPHA=0.8;
		double BETA=2;
		long UBOUND=2000;
		long LBOUND=1;
		long SRTT=0;

		//Data Transmission Queue Details.
		long Data_To_Send=0;
		boolean Send_PSH=false;			

		//Default Timer Values
		long User_Timeout=30000;
		long MSL_Timeout=50;
		long Persist_Timeout=200;
		long Delayed_ACK_Timeout=30;
		
		public void setUserTimeout(int timeout) {
			User_Timeout = timeout;
		}

		public long getRCV_WND() {
			return RCV_WND;
		}

		public int getRCV_NXT() {
			return RCV_NXT;
		}

		public int getISS() {
			return ISS;
		}

		public void setISS(int i) {
			ISS = i;
		}

		public void setIRS(int i) {
			IRS = i;
		}

		public void setRCV_NXT(int i) {
			RCV_NXT = i;
		}

		public int getState() {
			return Current_State;
		}

		public void setMSS(long mss) {
			this.MSS = mss;
		}

		public void setSND_UNA(int i) {
			this.SND_UNA = i;
		}

		public void setSND_NXT(int i) {
			this.SND_NXT = i;
		}

		public void setState(int state) {
			this.Previous_State = this.Current_State;
			this.Current_State = state;
			
		}
	}



}
